package cn.yx.common.mybatis.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.mapping.DtoDoMapping;
import cn.yx.common.core.pojo.PageDto;
import cn.yx.common.core.pojo.singletable.SingleCond;
import cn.yx.common.core.pojo.singletable.SingleDto;
import cn.yx.common.core.service.BaseService;
import cn.yx.common.core.util.json.jackson.JsonUtil;
import cn.yx.common.core.util.reflect.ReflectUtil;
import cn.yx.common.mybatis.constant.message.BaseServiceMessageKey;
import cn.yx.common.mybatis.constant.service.PojoPropertyConstant;
import cn.yx.common.mybatis.mapper.BaseMapper;
import cn.yx.common.mybatis.pojo.SingleDo;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * <p>BaseService接口的MyBatis实现类，使用BaseMapper数据访问类</p>
 *
 * @param <Do>   DO类
 * @param <Dto>  DTO类
 * @param <Cond> Cond类
 * @param <Id>   ID主键值类
 * @author Wgssmart
 */
@Slf4j
public abstract class AbstractMybatisService<Do extends SingleDo,
        Dto extends SingleDto,
        Cond extends SingleCond,
        Id extends Serializable>
        implements BaseService<Dto, Cond, Id> {

    private final BaseMapper<Do, Cond, Id> mapper;

    private final DtoDoMapping<Do, Dto> mapping;

    public AbstractMybatisService(BaseMapper<Do, Cond, Id> mapper, DtoDoMapping<Do, Dto> mapping) {
        this.mapper = mapper;
        this.mapping = mapping;
    }

    @Override
    public boolean save(Dto dto) {
        return mapper.insert(mapping.dtoToDo(dto)) == 1;
    }

    @Override
    public boolean save(List<Dto> dtoList) {
        return mapper.insertBatch(mapping.dtoToDo(dtoList)) > 0;
    }

    @Override
    public boolean save(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean saveOrUpdate(Dto dto) {
        boolean hasId = ReflectUtil.hasProperty(dto, PojoPropertyConstant.KEY_PROPERTY);
        if (!hasId) {
            // 如果DTO中没有id属性值，则直接保存到数据库
            return save(dto);
        }
        // 如果数据库中不存在id，则保存，否则更新
        return Objects.isNull(mapper.existId(getId(dto))) ? save(dto) : update(dto);
    }

    @Override
    public boolean saveOrUpdate(Dto dto, Cond updateCond) {
        List<Id> existIds = mapper.existIds(updateCond);
        if (CollectionUtil.isEmpty(existIds)) {
            // 如果根据条件在数据库中未找到数据，则保存DTO
            return save(dto);
        }
        return update(getUpdateDtoList(dto, existIds));
    }

    @Override
    public boolean saveOrUpdate(List<Dto> dtoList) {
        // 通过id集合判断数据库中是否存在数据
        List<Id> existIds = mapper.existBatchIds(getIds(dtoList));
        if (CollectionUtil.isEmpty(existIds)) {
            // 数据库中不存在数据，全部保存
            return save(dtoList);
        }
        List<Dto> saveDtoList = new ArrayList<>();
        List<Dto> updateDtoList = new ArrayList<>();
        for (Dto dto : dtoList) {
            if (!existIds.contains(getId(dto))) {
                // 如果数据库中不存在此id的数据，则保存
                saveDtoList.add(dto);
            } else {
                // 如果数据库中存在此id的数据，则更新
                updateDtoList.add(dto);
            }
        }
        return save(saveDtoList) && update(updateDtoList);
    }

    @Override
    public boolean saveOrUpdate(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean remove(Id id) {
        return mapper.deleteById(id) == 1;
    }

    @Override
    public boolean remove(List<Id> idList) {
        return mapper.deleteBatchIds(idList) > 0;
    }

    @Override
    public boolean remove(Cond deleteCond) {
        return mapper.delete(deleteCond) > 0;
    }

    @Override
    public boolean update(Dto dto) {
        currentVersion(dto);
        return mapper.updateById(mapping.dtoToDo(dto)) == 1;
    }

    @Override
    public boolean update(List<Dto> dtoList) {
        for (Dto dto : dtoList) {
            currentVersion(dto);
        }
        return mapper.updateBatchIds(mapping.dtoToDo(dtoList)) > 0;
    }

    @Override
    public boolean update(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean update(Dto dto, Cond updateCond) {
        List<Id> existIds = mapper.existIds(updateCond);
        return update(getUpdateDtoList(dto, existIds));
    }

    @Override
    public boolean updateAllColumn(Dto dto) {
        currentVersion(dto);
        return mapper.updateAllColumnById(mapping.dtoToDo(dto)) == 1;
    }

    @Override
    public boolean updateAllColumn(List<Dto> dtoList) {
        for (Dto dto : dtoList) {
            currentVersion(dto);
        }
        return mapper.updateAllColumnBatchIds(mapping.dtoToDo(dtoList)) > 0;
    }

    @Override
    public boolean updateAllColumn(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean updateAllColumn(Dto dto, Cond updateCond) {
        List<Id> existIds = mapper.existIds(updateCond);
        if (CollectionUtil.isEmpty(existIds)) {
            return true;
        }
        return updateAllColumn(getUpdateDtoList(dto, existIds));
    }

    @Override
    public Dto getOne(Id id) {
        return mapping.doToDto(mapper.selectById(id));
    }

    @Override
    public Dto getOne(Cond selectCond) {
        return mapping.doToDto(mapper.selectOne(selectCond));
    }

    @Override
    public Dto getOne(Cond selectCond, boolean throwEx) {
        List<Dto> existDtoList = listAll(selectCond);
        if (CollectionUtil.isEmpty(existDtoList)) {
            return null;
        }
        if (throwEx && existDtoList.size() > 1) {
            throw new CommonException(BaseServiceMessageKey.MORE_THAN_ONE_RESULT_FOUND_ERROR);
        }
        return existDtoList.get(0);
    }

    @Override
    public List<Dto> listAll() {
        return mapping.doToDto(mapper.selectList(null));
    }

    @Override
    public List<Dto> listAll(Cond selectCond) {
        return mapping.doToDto(mapper.selectList(selectCond));
    }

    @Override
    public List<Dto> listAll(List<Id> idList) {
        return mapping.doToDto(mapper.selectBatchIds(idList));
    }

    @Override
    public PageDto<Dto> page(Cond selectCond) {
        Integer pageNum = ReflectUtil.invokeGetter(selectCond, PojoPropertyConstant.PAGE_NUM_PROPERTY, Integer.class);
        Integer pageSize = ReflectUtil.invokeGetter(selectCond, PojoPropertyConstant.PAGE_SIZE_PROPERTY, Integer.class);
        PageHelper.startPage(pageNum, pageSize);
        List<Do> doList = mapper.selectPage(selectCond);
        PageDto<Dto> pageDto = new PageDto<>();
        pageDto.setTotal(((Page<Do>) doList).getTotal());
        pageDto.setResults(mapping.doToDto(doList));
        return pageDto;
    }

    @Override
    public long count() {
        return count(null);
    }

    @Override
    public long count(Cond selectCond) {
        return mapper.selectCount(selectCond);
    }

    /**
     * 获取DTO的id
     *
     * @param dto
     * @return
     */
    @SuppressWarnings({"unchecked"})
    private Id getId(Dto dto) {
        return (Id) ReflectUtil.invokeGetter(dto, PojoPropertyConstant.KEY_PROPERTY);
    }

    /**
     * 获取DTO集合中的所有id
     *
     * @param dtoList
     * @return
     */
    @SuppressWarnings({"unchecked"})
    private List<Id> getIds(List<Dto> dtoList) {
        List<Id> ids = new ArrayList<>();
        for (Dto dto : dtoList) {
            Id id = (Id) ReflectUtil.invokeGetter(dto, PojoPropertyConstant.KEY_PROPERTY);
            if (Objects.nonNull(id)) {
                ids.add(id);
            }
        }
        return ids;
    }

    /**
     * 获取需要更新的对象集合，为DTO对象设置数据库中已经存在的id，并添加到集合中
     *
     * @param dto
     * @param existIds
     * @return
     */
    @SuppressWarnings({"unchecked"})
    private List<Dto> getUpdateDtoList(Dto dto, List<Id> existIds) {
        List<Dto> updateDtoList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(existIds)) {
            for (Serializable existId : existIds) {
                Dto updateDto = (Dto) JsonUtil.toBean(JsonUtil.toJsonStr(dto), dto.getClass());
                ReflectUtil.invokeSetter(updateDto, PojoPropertyConstant.KEY_PROPERTY, existId);
                currentVersion(updateDto);
                updateDtoList.add(updateDto);
            }
        }
        return updateDtoList;
    }

    /**
     * 获取DTO版本号
     *
     * @param dto
     */
    private void currentVersion(Dto dto) {
        Integer version = mapper.selectVersionById(getId(dto));
        ReflectUtil.invokeSetter(dto, PojoPropertyConstant.VERSION_PROPERTY, version);
    }
}
